package cn.com.generaldata.os.service.indicator_evaluation.incr_analysis;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.Validate;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.entity.Record;
import org.nutz.dao.sql.Criteria;
import org.nutz.lang.Lang;
import org.nutz.lang.random.R;
import org.opensourcedea.dea.DEAProblem;
import org.opensourcedea.dea.ModelType;
import org.opensourcedea.dea.VariableOrientation;
import org.opensourcedea.dea.VariableType;

import cn.com.generaldata.jsme_indicator_system.entity.IndicatorEvaluation;
import cn.com.generaldata.jsme_indicator_system.util.Constants;
import cn.com.generaldata.os.pojo.IndicatorEvaluationResult;
import cn.com.generaldata.os.service.indicator_constant.ProvinceIndicatorConstant;
import cn.com.generaldata.os.service.indicator_evaluation.CalculateService;
import cn.com.generaldata.os.service.indicator_evaluation.CalculateServiceHelper;
import cn.com.generaldata.os.util.BeanMapper;
import cn.com.generaldata.os.vo.IndicatorAccountVO;

/**
 * DEA相对效率评估计算类
 */
public class DEARelativeEfficiencyCalculateService implements CalculateService {

	private Dao dao;

	private Dao indicatorSystemDao;

	private CalculateServiceHelper helper;

	private int rows = 11;

	private int columns = 4;

	private String[] varNames = { "GOP（亿元）",
			"资本存量",
			"涉海就业（万人）",
			"科技投入（万元）"
	};

	private VariableOrientation[] varOrientations = {
			VariableOrientation.OUTPUT,
			VariableOrientation.INPUT,
			VariableOrientation.INPUT,
			VariableOrientation.INPUT
	};

	private VariableType[] varTypes = {
			VariableType.STANDARD,
			VariableType.STANDARD,
			VariableType.STANDARD,
			VariableType.STANDARD
	};

	@Override
	public void calculateIndicator(IndicatorEvaluation indicator, IndicatorAccountVO indicatorAccountVO) {
		DEAProblem dea = new DEAProblem(rows, columns);

		// Set the DEA Problem Model Type (CCR Input Oriented).
		dea.setModelType(ModelType.CCR_I);

		// Set the DEA Problem DMU Names where testDMUName is a double[].
		dea.setDMUNames(getDumpNames());

		// Set the DEA Problem Variable Names where testVariableName is a String[].
		dea.setVariableNames(varNames);

		// Set the DEA Problem Variable Orientation where testVariableOrientation is a VariableOrientation[].
		dea.setVariableOrientations(varOrientations);

		// Set the DEA Problem Variable Types where testVariableTypes is a VariableType[].
		dea.setVariableTypes(varTypes);

		/*
		 * Set the DEA Problem Data Matrix where testDataMatrix is a double[] [].
		 * Each row of the Matrix corresponds to the DMU in the DMUNames array.
		 * Each Column of the Matrix corresponds to the Variable in the Variables Arrays.
		 */
		dea.setDataMatrix(getDataMatrix(indicatorAccountVO));

		try {
			dea.solve();
			// Get the solution Objectives
			double[] objectives = dea.getObjectives();
			saveResult(indicator, indicatorAccountVO, objectives);
		} catch (Exception e) {
			throw Lang.wrapThrow(e);
		}

	}

	/**
	 * 保存结果.
	 * 
	 * @param indicatorAccountVO
	 * @param objectives
	 */
	private void saveResult(IndicatorEvaluation indicator, IndicatorAccountVO indicatorAccountVO, double[] objectives) {
		IndicatorAccountVO vo = BeanMapper.map(indicatorAccountVO, IndicatorAccountVO.class);
		String[] dumpNames = getDumpNames();
		for (int i = 0, lenght = objectives.length; i < lenght; i++) {
			double result = objectives[i];
			String areaId = dumpNames[i];
			vo.setAreaId(areaId);
			IndicatorEvaluationResult ier = createIndicatorEvaluationResult(indicator, vo);
			Double r = new BigDecimal(result).setScale(10, BigDecimal.ROUND_HALF_UP).doubleValue();
			ier.setIndicatorValue(r);
			Criteria cri = getIndicatorAccountingDataCri(indicator, vo);

			Record record = dao.fetch("tbl_indicator_evaluation_res", cri);
			if (record != null) {
				dao.clear("tbl_indicator_evaluation_res", cri);
			}

			saveIndicatorEvaluationResult(ier);
		}
	}

	private String[] getDumpNames() {
		List<String> areaIds = new ArrayList<String>(ProvinceIndicatorConstant.OTHER_COASTAL_PROVINCES);
		areaIds.add(5, ProvinceIndicatorConstant.PROVICE_AREA_ID);
		return Lang.collection2array(areaIds);
	}

	/**
	 * 构造DEA计算所需的二维数组.
	 * 
	 * @param indicatorAccountVO
	 * @return
	 */
	private double[][] getDataMatrix(IndicatorAccountVO indicatorAccountVO) {
		double[][] result = new double[rows][columns];
		String[] dumpNames = getDumpNames();
		IndicatorAccountVO vo = BeanMapper.map(indicatorAccountVO, IndicatorAccountVO.class);

		for (int i = 0; i < rows; i++) {
			String areaId = dumpNames[i];
			vo.setAreaId(areaId);
			result[i][0] = getGOP(vo);
			result[i][1] = getCapitalInput(vo);
			result[i][2] = getOceanRelatedEmp(vo);
			result[i][3] = getScienceTechnologyInput(vo);
		}

		return result;
	}

	private Double getGOP(IndicatorAccountVO indicatorAccountVO) {
		Double GOP = helper.getInputOrAccountingIndicatorValue(indicatorAccountVO, indicatorAccountVO.getYear(),
				ProvinceIndicatorConstant.GOP_INDICATOR_NAME);
		Validate.isTrue(GOP != null);
		return GOP;
	}

	private Double getGDP(IndicatorAccountVO indicatorAccountVO) {
		Double GDP = helper.getInputOrAccountingIndicatorValue(indicatorAccountVO, indicatorAccountVO.getYear(),
				ProvinceIndicatorConstant.GDP_INDICATOR_NAME);
		Validate.isTrue(GDP != null);
		return GDP;
	}

	/**
	 * 获取海洋资本投入，资本投入计算公式：资本投入 = 海洋生产总值 / 地区生产总值 * 全社会固定投资
	 * 
	 * @param indicatorAccountVO
	 * @return
	 */
	private Double getCapitalInput(IndicatorAccountVO indicatorAccountVO) {
		Double GOP = getGOP(indicatorAccountVO);
		Double GDP = getGDP(indicatorAccountVO);
		Double capitalFormation = helper.getInputOrAccountingIndicatorValue(indicatorAccountVO,
				indicatorAccountVO.getYear(),
				ProvinceIndicatorConstant.TOTAL_INVESTMENT_FIXED_ASSETS_INDICATOR_NAME);
		Validate.isTrue(capitalFormation != null && !Double.valueOf("0").equals(capitalFormation));
		return GOP / GDP * capitalFormation;
	}

	/**
	 * 获得海洋科学技术投入
	 * 
	 * @param indicatorAccountVO
	 * @return
	 */
	private Double getScienceTechnologyInput(IndicatorAccountVO indicatorAccountVO) {
		Double scienceTechnologyInput = helper.getInputOrAccountingIndicatorValue(indicatorAccountVO,
				indicatorAccountVO.getYear(), ProvinceIndicatorConstant.SCIENCE_TECHNOLOGY_INPUT_INDICATOR_NAME);

		Validate.isTrue(scienceTechnologyInput != null);
		return scienceTechnologyInput;
	}

	/**
	 * 获得涉海就业人数
	 * 
	 * @param indicatorAccountVO
	 * @return
	 */
	private Double getOceanRelatedEmp(IndicatorAccountVO indicatorAccountVO) {
		Double result = helper.getInputOrAccountingIndicatorValue(indicatorAccountVO,
				indicatorAccountVO.getYear(),
				ProvinceIndicatorConstant.OCEAN_EMPLOYED_INDICATOR_NAME);
		Validate.isTrue(result != null);
		return result;
	}

	protected void saveIndicatorEvaluationResult(IndicatorEvaluationResult ier) {
		// 保存新记录
		Chain chain = Chain.make("id", R.UU16());
		chain.add("indicator_id", ier.getIndicatorId());
		chain.add("area_id", ier.getAreaId());
		chain.add("time_type", ier.getTimeType());
		chain.add("year", ier.getYear());
		if (ier.getTimeType() == Constants.REPORT_TIME_MONTH) {
			chain.add("month", ier.getMonth());
		} else if (ier.getTimeType() == Constants.REPORT_TIME_YEAR) {
			chain.add("year_time", ier.getYearTime());
		} else {
			chain.add("quarter", ier.getQuarter());
		}

		chain.add("indicator_value", ier.getIndicatorValue());

		dao.insert("tbl_indicator_evaluation_res", chain);
	}

	protected IndicatorEvaluationResult createIndicatorEvaluationResult(IndicatorEvaluation indicator,
			IndicatorAccountVO indicatorAccountVO) {
		IndicatorEvaluationResult result = BeanMapper.map(indicatorAccountVO, IndicatorEvaluationResult.class);
		result.setIndicatorId(indicator.getId());
		return result;
	}

	protected Criteria getIndicatorAccountingDataCri(IndicatorEvaluation indicator,
			IndicatorAccountVO indicatorAccountVO) {
		Criteria cri = Cnd.cri();
		cri.where().and("indicator_id", "=", indicator.getId());
		cri.where().and("time_type", "=", indicatorAccountVO.getTimeType());
		cri.where().and("year", "=", indicatorAccountVO.getYear());
		cri.where().and("area_id", "=", indicatorAccountVO.getAreaId());
		if (indicatorAccountVO.getTimeType().intValue() == Constants.REPORT_TIME_MONTH) {
			cri.where().and("month", "=", indicatorAccountVO.getMonth());
		} else if (indicatorAccountVO.getTimeType() == Constants.REPORT_TIME_YEAR) {
			cri.where().and("year_time", "=", indicatorAccountVO.getYearTime());
		} else {
			cri.where().and("quarter", "=", indicatorAccountVO.getQuarter());
		}
		return cri;
	}

	@Override
	public void setDao(Dao dao) {
		this.dao = dao;
	}

	@Override
	public void setIndicatorSystemDao(Dao indicatorSystemDao) {
		this.indicatorSystemDao = indicatorSystemDao;
	}

	@Override
	public void setCalculateServiceHelper(CalculateServiceHelper calculateServiceHelper) {
		this.helper = calculateServiceHelper;
	}

}
